// Package ecdsa ...
package ecdsa

import (
	"crypto/ecdsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/asn1"
	"encoding/pem"
	"fmt"
	"math/big"

	"github.com/pkg/errors"

	"nft-server/logger"
)

type signature struct {
	R, S *big.Int
}

func verifyCert(cert *x509.Certificate, caCert []byte) error {
	var ca *x509.Certificate
	b, _ := pem.Decode(caCert)
	if b == nil {
		return errors.Errorf("certificate invalid")
	}
	ca, err := x509.ParseCertificate(b.Bytes)
	if err != nil {
		return err
	}
	return cert.CheckSignatureFrom(ca)
}

// to verify the certificate provided by client is signed by the specific CA or not,
// and to verify the signature is valid or not
func Verify(cert *x509.Certificate, cipherText []byte, text []byte, caCert []byte) error {
	if err := verifyCert(cert, caCert); err != nil {
		return errors.Errorf("certificate is invalid err: %s", err)
	}
	hasher := sha256.New()
	if _, err := hasher.Write(text); err != nil {
		return errors.Errorf("hasher write failed err:%v", err)
	}
	hashed := hasher.Sum(nil)
	return verifySig(cert, cipherText, hashed)
}

func verifySig(cert *x509.Certificate, cipherText []byte, text []byte) error {
	r, s, err := unmarshalECDSASignature(cipherText)
	if err != nil {
		return errors.Errorf("unmarshal signedData failed, err: %s", err)
	}
	publicKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
	if !ok {
		return errors.Errorf("publickey is not of type ecdsa")
	}
	check := ecdsa.Verify(publicKey, text, r, s)
	if check == true {
		return nil
	}
	return errors.Errorf("verify fail")
}

func unmarshalECDSASignature(raw []byte) (*big.Int, *big.Int, error) {
	sig := new(signature)
	_, err := asn1.Unmarshal(raw, sig)
	if err != nil {
		logger.Error("failed unmashalling signature", err)
		return nil, nil, errors.WithMessage(err, "failed unmashalling signature [%s]")
	}

	// Validate sig
	if sig.R == nil {
		return nil, nil, fmt.Errorf("invalid signature, r must be different from nil")
	}
	if sig.S == nil {
		return nil, nil, fmt.Errorf("invalid signature, s must be different from nil")
	}

	if sig.R.Sign() != 1 {
		return nil, nil, fmt.Errorf("invalid signature, r must be larger than zero")
	}
	if sig.S.Sign() != 1 {
		return nil, nil, fmt.Errorf("invalid signature, s must be larger than zero")
	}

	return sig.R, sig.S, nil
}
